
/* The SortController class is the "brains" of the sorting operations.  As the 
 * shepherd of a flock of sorts, it keeps an array of Sort objects.  The 
 * SortController starts off the sort trials when the SortApp gets the "go!" 
 * signal.  The SortController generates the data and forks off the sort 
 * threads.  It keeps track of when the sorts finish, starts the next sort 
 * when the sorts are running in series, and lets the SortApp know when all 
 * the sorts have finished. Most of the controls of the Parameters panel send 
 * their target-action methods to the SortController and it sends the changes 
 * on to the sorts as necessary.  The SortController is also the text delegate 
 * for the various text fields and forms of the Parameters panel.  It does 
 * entry checking to make sure the user enters reasonable values.
 *
 * Author: Julie Zelenski, NeXT Developer Support
 * You may freely copy, distribute and reuse the code in this example.  
 * NeXT disclaims any warranty of any kind, expressed or implied, as to 
 * its fitness for any particular use.
 */

#import <appkit/appkit.h>
#import "SortController.h"
#import "BubbleSort.h"
#import "InsertionSort.h"
#import "MergeSort.h"
#import "QuickSort.h"
#import "SelectionSort.h"
#import "ShellSort.h"
#import "SortApp.h"
#import "SortView.h"
#import <libc.h>
#import <math.h>
#import <mach/cthreads.h>


extern BOOL Abort;		// global variable to signal abort 

 
@implementation SortController:Object


- init;
/* The initialization method is called once, when unarchiving the nib section.  
 * There is only one instantiation of the SortConroller object. This method 
 * creates a new sort object for each algorithm and initializes some instance
 * variables.
 */
{  
    [super init];
    srandom(time(0));     		// Seed random for data generating
    					// Create on of each sort object
    sort[BUBBLE_SORT] = [[BubbleSort allocFromZone:[self zone]] init];
    sort[INSERTION_SORT] = [[InsertionSort allocFromZone:[self zone]] init];
    sort[MERGE_SORT] = [[MergeSort allocFromZone:[self zone]] init];
    sort[QUICK_SORT] = [[QuickSort allocFromZone:[self zone]] init];
    sort[SELECTION_SORT] = [[SelectionSort allocFromZone:[self zone]] init];
    sort[SHELL_SORT] = [[ShellSort allocFromZone:[self zone]] init];
    sortOn[QUICK_SORT] =  sortOn[INSERTION_SORT] = sortOn[SHELL_SORT] = YES;
    sortOn[BUBBLE_SORT] = sortOn[SELECTION_SORT] = sortOn[MERGE_SORT] = NO;
    numSorts = 3;
    parallel = YES;
    numElements = 50;
    percentSorted = 0;
    return self;
}

#define SIZE 1		// tags for different text fields
#define PERCENT 2
#define COMPARE 3
#define MOVE 4
#define FCALL 5	


/* TEXT DELEGATE METHODS */


- (BOOL)textWillEnd:textObject;
/* Checks to make sure that the entry in each of the text fields is reasonable. 
 * Different values are reasonable based on what the field is.  You can get the 
 * field or form being edited by asking the text object for its delegate.  If 
 * the value is unreasonable, reject it, and set the value back to the closest
 * reasonable value. Then it sends the action to the target so that change is 
 * recorded.
 */
{   
    BOOL entryOK = YES;
    id form;
    int value;
    
    form = [textObject delegate];
    value = [[form selectedCell] intValue];
    
    switch ([form selectedTag]) {
      	case SIZE: 	entryOK = (value > 0 );
			if (value>5000) [[form selectedCell] setIntValue:5000];
			break;
    	case PERCENT:   entryOK = ((value >= 0) && (value <= 100));
			break;
  	case FCALL: 	  
	case MOVE: 	
  	case COMPARE: 	entryOK = (value >= 0);
			break;
    }
    if (entryOK) [form sendAction:[form action] to:[form target]];
    return (!entryOK);
}



/*  TARGET-ACTION METHODS (for controls in the Sort Parameters panel) */
 
- changeAnimate:sender
/* Changes whether the sorts highlight their comparisions. SortController 
 * simply passes this method onto each of the sorts.
 */
{   int c;
    BOOL value = (BOOL)[sender state];
    
    for (c = 0; c < NUM_SORT_TYPES; c++) 
        [sort[c] setAnimate:value];
    return self;
}


- changeParallel:sender
/* Changes whether the sorts run in parallel or series. SortController needs 
 * this to know whether to launch all sort threads at once, or to wait until 
 * one has finished to start the next sort.
 */
{
    parallel = (BOOL)[sender selectedTag];
    return self;
}


- changeSpeed:sender;
/* Changes at what speed the sorts are running.  SortController simply passes 
 * this method onto each of the sorts.
 */
{
    int c, speed;
    speed = [(Slider *)sender maxValue] - [sender intValue];
    for (c = 0; c < NUM_SORT_TYPES; c++) 
       [sort[c] setSpeed:speed];
    return self;
}


- changeSortOn:sender
/* Changes whether a selected sort is set to run or not.  Increments or 
 * decrements the number of sorts in this trial as appropriate.
 */
{   BOOL on; 

    on = (BOOL)[[sender selectedCell] state];
    sortOn[[sender selectedTag]] = on;
    if (on) 
    	numSorts++;
    else 
    	numSorts--;
    return self;
}


	  
- changeDataSet:sender;
/* Changes the size or percent sorted of a data set, uses tag to determine 
 * which.
 */
{
    switch ([sender selectedTag]) {
        case SIZE:	numElements = [sender intValue];
			break;
        case PERCENT:   percentSorted = [sender intValue];
			break;
    }
    return self;
}


- changeTickValue:sender;
/* Changes the tick cost of a certain operation, uses tag to determine which.  
 * SortController simply passes this method onto each of the sorts.
 */
{
    int c, value;
    
    value = [sender intValue];
    for (c = 0; c < NUM_SORT_TYPES; c++) 
        [sort[c] setTicks:value for:[sender selectedTag]];
    return self;
}




/* PRIVATE METHODS */


- setSortWindow:anObject
/* The sortWindow is where all the animation takes place.
 */
{   
    sortWindow = anObject;  
    [[sortWindow contentView] setFlipped:YES];     
    return self;
}


- setUpWindow;
/* This is the method that adjusts the sorting window for a new trial.  It 
 * resizes the window to fit all the needed sort views and then adds those 
 * sortviews as subviews of the content view.  It removes any sortviews of
 * sorts that aren't scheduled to run in this trial.
 */
{
    NXPoint pt = {2.0,2.0};
    NXRect windowRect,headerRect;
    float newHeight;
    int c;
    
    [sortWindow disableFlushWindow];
    [sortWindow getFrame:&windowRect];
    [windowHeader getFrame:&headerRect];
    newHeight = headerRect.size.height + numSorts*(VIEW_HEIGHT+2) + 24;
    windowRect.origin.y += windowRect.size.height - newHeight;
    windowRect.size.height = newHeight;
    [sortWindow placeWindow:&windowRect];
    [windowHeader moveTo:headerRect.origin.x :-3];
    pt.y = headerRect.size.height;
    for (c = 0; (c < NUM_SORT_TYPES); c++) {
        if (sortOn[c]) {
	    [[sort[c] view] moveTo:pt.x :pt.y];
	    [[sortWindow contentView] addSubview:[sort[c] view]];
	    pt.y += VIEW_HEIGHT+2.0;
	} else {
	    [[sort[c] view] removeFromSuperview]; 
	}
     }   
     [sortWindow display];
     [[[sortWindow reenableFlushWindow] flushWindow] orderFront:self];
     return self;
}


- (int *)newDataSet
/* This is the method to generate a new data set.  Basically, it cycles 
 * through a loop numElements times, getting a new number for each position 
 * of the array.  For completely random data, each element is randomly 
 * generated.  For partially sorted data, only some of the elements are random, 
 * others are inserted in sorted position.  Each time through the loop, a 
 * random number is first generated to determine whether this array element 
 * will be random. If the random number is greater than the percent sorted, 
 * it will generate a random number for that array element, otherwise it will 
 * assign an element in sorted order to that array element. */
{    
    int max,c,*data;
    
    data = (int *)calloc(numElements,sizeof(int));
    max = floor((VIEW_HEIGHT-15.0)/(ceil((float)numElements/(VIEW_WIDTH-6))));    
    for (c = 0; c < numElements ;c++) {
        cthread_yield();  	// give the interface a change
	if (((random() % 100) + 1) > percentSorted)
            data[c] = (random() % (int)(max-1))+ 1;
     	else 
	    data[c] = (int)(c*(max/(float)numElements)) +1;
    }
    return data;
}


- setUpData;
/* This method sends a message to itself to generate a new data set and 
 * then passes that data set along to the sorts who are scheduled to run 
 * so they can make a copy of it.  The sorts will set up their sortviews 
 * and draw the data set. (by messaging back to main thread)
 */
{    
    int c, *address;
       
    address = [self newDataSet];
    for (c = 0; c < NUM_SORT_TYPES; c++) {
        if (sortOn[c]) 
            [sort[c] setSize:numElements address:address];
    }
    cfree(address);
    return self;
}


static any_t sortInThread(aSort)
  id aSort;
{
    return [aSort sort];
}

 
- doSort;
/* This method is called to run a complete trial.  It is launched as a thread, 
 * so from here on out, I can no longer safely draw and must message to the 
 * main thread.  This method generates the data for the trial, sets up all 
 * the sort views, starts the tick counter, and then launches the sort threads.
 */
{   int c;

    [self setUpData];
    if (!Abort) 
    	[NXApp startTickCounter]; 
    for (c = 0; c < NUM_SORT_TYPES; c++) {
        if (sortOn[c]) {
	    cthread_detach(cthread_fork(sortInThread,sort[c]));
	    if (!parallel) 
	    	break;
	}	          
    }	
    return self;
}


static any_t doSort(self)
  id self;
{
    return [self doSort];
}



/* PUBLIC METHODS */

- (BOOL)startSort;
/* The SortApp object calls this method after the Go button is clicked.  
 * If there are no sorts to run or there is no data set, this method returns 
 * NO to the SortApp. Otherwise, I resize and set up the sorting window and 
 * launch a thread to "doSort" which generates the data and launches the sort 
 * threads.  By launching a thread, this method will return almost immediately, 
 * so the user isn't locked out while I do the work.
 */
{
    if (!numSorts || !numElements) 
        return NO;
    else {
        sortsFinished =  0;
        [self setUpWindow];
        cthread_detach(cthread_fork(doSort,self)); 
	return YES;
    } 
}


- sortFinished:(int)sortNum;
/* When a sort finishes, it messages back to the main thread who does the final
 * display.  The SortApp then calls this method so the SortController can 
 * launch the next sort if necessary (i.e. if the sorts are running in series).  
 * The SortController also keeps track of how many sorts still need to finish.  
 * If all sorts have finished, the SortController lets the SortApp know, so it 
 * can clean up, stop the tick counter and re-enable all the controls in the 
 * Parameters panel.
 */
{
    sortsFinished++;
    if (sortsFinished == numSorts) 	// if all sorts finished,
        [NXApp allFinished];		// let SortApp know this
    else if (!parallel) {		// if in series, launch next sort
        while (!sortOn[++sortNum]) ;
	[NXApp startTickCounter]; 
        cthread_detach(cthread_fork(sortInThread,sort[sortNum]));
    }
  return self;
}


- findSortWithNum:(int)sortNum;
/* Simply returns the sort of the specified number.  Called by the SortApp 
 * who has only the number of the sort, but needs the actual sort object.
 */
{
    return sort[sortNum];
}


@end